﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

using Acceron.University.DBAccess;

namespace GA_Scheduler
{
    public class Schedule
    {
        //private List<DaySchedule> _genes;
        private List<Gen> _genes;
        int gencount = 0;

        public List<Gen> Genes { get { return _genes; } private set { _genes = value; } }

        public float Fitness { get; private set; }

        public Schedule()
        {
            this._genes = new List<Gen>();
        }

        /// <summary>
        /// Crossovers two chromosomes to one
        /// </summary>
        /// <param name="parent">second chromosome</param>
        /// <returns>Crossoved chromosome</returns>
        public Schedule Crossover(Schedule parent)
        {
            if (rand() % 100 > GA_Scheduler.Properties.Settings.Default.CrossoverProbability)
            {
                //return this.Copy();
                return null;
            }

            
            var newSch = new Schedule();

            int crossNum = 0;
            do
            {
                newSch.Genes.Clear();
                // number of classes
                //int size = Configuration.Instance.NumberOfClasses;
                int size = Genes.Count;

                bool[] cp = new bool[size];

                // determine crossover point (randomly)
                for (int i = GA_Scheduler.Properties.Settings.Default.NumberOfCrossoverPoints; i > 0; i--)
                {
                    while (true)
                    {
                        int p = rand() % size;
                        if (!cp[p])
                        {
                            cp[p] = true;
                            break;
                        }
                    }
                }

                bool first = rand() % 2 == 0;
                for (int i = 0; i < this.Genes.Count; i++)
                {
                    if (first)
                    {
                        newSch.Genes.Add(this.Genes[i]);
                    }
                    else
                    {
                        newSch.Genes.Add(parent.Genes[i]);
                    }
                    // crossover point
                    if (cp[i])
                        // change source chromosome
                        first = !first;
                }

                newSch.CalculateFitness();
                crossNum++;
            }
            while (newSch.CheckOverlaps());

            return newSch;
        }

        /// <summary>
        /// Mutation chromosome with mutation probability
        /// </summary>
        public void Mutation()
        {
            var rooms = Configuration.Instance.Rooms;
            // check probability of mutation operation
            if (rand() % 1000 > GA_Scheduler.Properties.Settings.Default.MutationProbability)
                return;

            var copied = this.Copy();
            do
            {
                copied = this.Copy();
                for (int i = 0; i < GA_Scheduler.Properties.Settings.Default.MutationSize; i++)
                {
                    int mpos = rand() % this._genes.Count;
                    var gen = copied.Genes[mpos];

                    gen.DayOfWeek = rand() % GA_Scheduler.Properties.Settings.Default.CountDays;
                    gen.Room = rooms[rand() % Configuration.Instance.NumberOfRooms];
                    gen.Pair = rand() % (12 - gen.Class.Duration);

                }
            }
            while (copied.CheckOverlaps());
            
            this._genes = copied._genes;

            CalculateFitness();
        }

       
        /// <summary>
        /// Calculates fitness for chromosome. Than more than better.
        /// </summary>
        public void CalculateFitness()
        {
            int score = 0;
            
            if (gencount == 0)
                gencount = _genes.Count;

            foreach (var pen in PenaltyManager.Penalties)
            {
                if (pen.Execute(this))
                    score -= pen.CalculatedScore;
            }

            Fitness = score;

        }

        /// <summary>
        /// Gets a copy of this chromosome
        /// </summary>
        /// <returns>Schedule with the same parametres</returns>
        public Schedule Copy()
        {
            Schedule sch = new Schedule();
            sch.Fitness = this.Fitness;

            for (int i = 0; i < this._genes.Count; i++)
            {
                sch._genes.Add(this._genes[i].Copy());
            }

            return sch;

        }

        private static Schedule _prototype;

        /// <summary>
        /// Checks room overlapping
        /// </summary>
        /// <param name="dSch"></param>
        /// <returns>True if room already used by other groups</returns>
        public bool CheckRoomsOverlap(Gen dSch)
        {
           var rOv = _genes.FirstOrDefault((g) => g.DayOfWeek == dSch.DayOfWeek && g.Class_id != dSch.Class_id && (g.Pair == dSch.Pair || 
                    (g.Pair > dSch.Pair && g.Pair <= dSch.Pair + dSch.Class.Duration - 1)
                    || (g.Pair < dSch.Pair && g.Pair + g.Class.Duration - 1 >= dSch.Pair)) && g.Room_id == dSch.Room_id);

            if (rOv == null)
                return false;
            else
                return true;

        }

        public bool CheckSizeOfRoom(Gen dSch)
        {
            if (dSch.Class.NumberOfSeats() > dSch.Room.Size)
                return true;

            return false;
                        
        }

        /// <summary>
        /// Checks professors for overlapping
        /// </summary>
        /// <param name="dSch"></param>
        /// <returns>return true if professor is busy</returns>
        public bool CheckProfessorsOverlap(Gen dSch)
        {
            var pOv = _genes.FirstOrDefault(g => g.DayOfWeek == dSch.DayOfWeek && g.Class_id != dSch.Class_id && (g.Pair == dSch.Pair ||
                (g.Pair > dSch.Pair && g.Pair <= dSch.Pair + dSch.Class.Duration - 1)
                || (g.Pair < dSch.Pair && g.Pair + g.Class.Duration - 1 >= dSch.Pair)) 
                && g.Class.Prof_ID == dSch.Class.Prof_ID );

            if (pOv == null)
                return false;
            else
                return true;
        }

        /// <summary>
        /// Checks overlap gor groups
        /// </summary>
        /// <param name="dSch"></param>
        /// <returns>true if any of groups already learns in this day and hour</returns>
        public bool CheckGroupOverlap(Gen dSch)
        {
            var beforeSelected = (from g in Genes
                                 where (g.DayOfWeek == dSch.DayOfWeek) && g.Class_id != dSch.Class_id &&
                                 (g.Pair == dSch.Pair || 
                                 (
                                 (g.Pair > dSch.Pair && g.Pair <= dSch.Pair + dSch.Class.Duration - 1) ||
                                 (g.Pair < dSch.Pair && g.Pair + g.Class.Duration - 1 >= dSch.Pair) 
                                 )
                                 )
                                 select g);

            bool over = false; 
            foreach (var gr in dSch.Class.GroupClasses)
            {
                var grOv = beforeSelected.FirstOrDefault(g => g.Class.GroupClasses.FirstOrDefault(t => t.Groupd_ID == gr.Groupd_ID) != null);
 
                if (grOv != null)
                {
                    over = true;
                    break;
                }
            }

            return over;
        }

        /// <summary>
        /// Checks all overlappings
        /// </summary>
        /// <returns></returns>
        private bool CheckOverlaps()
        {
            //if (Environment.ProcessorCount > 1)
            {
                foreach (var gen in Genes)
                {
                    var grOv = CheckGroupOverlap(gen);
                    var profOv = CheckProfessorsOverlap(gen);
                    var rOv = CheckRoomsOverlap(gen);
                    var rsOV = CheckSizeOfRoom(gen);

                    if (grOv || profOv || rOv || rsOV)
                    {
                        return true;
                    }
                }
            }
            
            return false;
        }

        /// <summary>
        /// Generate new prototype of chromosome.
        /// </summary>
        /// <returns></returns>
        protected static Schedule CreatePrototype()
        {
            Schedule newSch = new Schedule();
            var rooms = Configuration.Instance.Rooms.OrderBy(r => r.Size).ToArray();
            var classes = Configuration.Instance.Classes;


            int day = 0;
            int hour = 0;

            foreach (var cl in Configuration.Instance.Classes)
            {
                Gen newPair = new Gen()
                {
                    DayOfWeek = day,
                    Pair = hour,
                    Class = cl,
                    Room = rooms[rand() % Configuration.Instance.NumberOfRooms]
                };

                #region Professors and groups overlap checking

                if (newSch.CheckProfessorsOverlap(newPair) || newSch.CheckGroupOverlap(newPair))
                {
                    bool alwayOverlap = true;

                    for (int j = day; j < GA_Scheduler.Properties.Settings.Default.CountDays; j++)
                    {
                        newPair.DayOfWeek = j;

                        for (int i = hour; i < 12; i++)
                        {
                            newPair.Pair = i;
                            if (!newSch.CheckGroupOverlap(newPair) && !newSch.CheckProfessorsOverlap(newPair))
                            {
                                alwayOverlap = false;
                                break;
                            }
                        }

                        if (!alwayOverlap)
                            break;

                        
                    }

                    if (alwayOverlap)
                        throw new Exception("Professors or groups always overlapped");
                }
                #endregion

                #region Room overlap checking

                int sizeNeeded = newPair.Class.NumberOfSeats();
                if (newSch.CheckRoomsOverlap(newPair) || newSch.CheckSizeOfRoom(newPair))
                {
                    bool alwayOverlap = true;
                    for (int i = 0; i < Configuration.Instance.NumberOfRooms; i++)
                    {
                        
                        newPair.Room = rooms[i];
                        if (!newSch.CheckRoomsOverlap(newPair) && !newSch.CheckSizeOfRoom(newPair))
                        {
                            alwayOverlap = false;
                            break;
                        }
                    }

                    if (alwayOverlap)
                    {
                        throw new Exception("No free rooms found for course " + cl.Course.Name);
                    }
                }

                newSch.Genes.Add(newPair);
                #endregion

            }

           
            return newSch;
        }



        /// <summary>
        /// Generates new chromosome with random parametres
        /// </summary>
        /// <returns>Generated schedule with random parametres</returns>
        public static Schedule GetFromPrototype()
        {
            if (_prototype == null)
            {
                _prototype = CreatePrototype();
                _prototype.CalculateFitness();
                return _prototype.Copy();
            }

            _prototype.Mutation();
            
            return _prototype.Copy();
        }

        /// <summary>
        /// C++ like helper-function
        /// </summary>
        /// <returns>Int32 pseudo-randomly generated number</returns>
        private static int rand()
        {
            return Configuration.Instance.Rand.Next();
        }
    }
}
